package Memetic;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.text.SimpleDateFormat;
import java.io.*;
import java.util.Date;

import util.*;
import random.TestClusterBuilder;
import instrumentation.TrackerCoverage;
import GA.*;
import localSearch.*;
import ReadWriteFieldsRelations.*;
import Operations.ValuesGenerator;
public class MemeticAlgorithm {
protected static Logger logger = Logger.getLogger("Memetic Algorithm class.");	
private GAInfo     gaInfo     = new GAInfo();
private TestClusterBuilder TestCluster;
private  TrackerCoverage track=  TrackerCoverage.getTracker();
public static MethodRelations relations;
private  long startTime;
private  long elapsed;

//private static  long TestTimeout = 60 * 500; /* Timeout at 30 seconds */
private static  long TestTimeout = 60 * 1000 * 15; /* Timeout at 60 seconds */


private void initialize() throws ClassNotFoundException,IOException{
	//Read the input file
	System.out.println("CUT is:" + GAConfig.classFilePath);
//	if (GAConfig.SaveData)
//  		Log.log("\nCUT is:" + GAConfig.classFilePath);
	 Set<Class<?>> allClasses = new LinkedHashSet<Class<?>>();
      //get class from file
      allClasses.addAll(TestClusterBuilder.readClassFromFile());

      if (!GAConfig.Silent) {
			System.out.println("There are " + allClasses.size()
					+ " classes as input");
			System.out.println(allClasses);
	      }
	      Set<Class<?>> classesToTest = TestClusterBuilder.filterUntestableClasses(allClasses);
	      if (!GAConfig.Silent){
	    	  System.out.println("There are " + classesToTest.size() + " classes to test after filtering");
	    	  System.out.println(classesToTest);
	      }	  
 	  
	// calculate the test cluster and the set of types involved in the
	 TestCluster = new TestClusterBuilder(classesToTest);		
	 gaInfo.setTestCluster(TestCluster);
     //initialize the method relations
     relations = new MethodRelations(classesToTest);
     relations.buildRelations();
     gaInfo.setMethodRelations(relations);
	//Generate random population
	Chromosome[] population = Population.randomPopulationInitializer(TestCluster);
	
	gaInfo.setPopulation(population);
	
	this.gaInfo.setGeneration(0);
	this.gaInfo.setPopulationFitness(0);
}
private void execute() {
	//Calculate fitness of initial population
	 if (!GAConfig.Silent) 
		 	System.out.println("\t **** execute generation "+ this.gaInfo.getGeneration() +" elapsed time: "+ elapsed + " of "+TestTimeout);
	if (GAConfig.PrintPopulation) System.out.println(Population.toString(gaInfo));
//	if (GAConfig.SaveData){
//		Log.log("\n\t **** execute generation "+ this.gaInfo.getGeneration() +" elapsed time: "+ elapsed + " of "+TestTimeout);
//	}
	Chromosome[] population=this.gaInfo.getPopulation();

	float populationFitness=0;
	for(int i=0; i<population.length && elapsed < TestTimeout; i++) {
		Chromosome individual=population[i];

//		System.out.println("MemeticAlgorithm.execute : "+individual.toString());
//		System.out.println("---------------------------------------------");

		ReflectionExecutor.execute(individual);
		float individualFitness= track.calculateFitness(individual);
		populationFitness+=individualFitness;
		individual.setFitness(individualFitness);
//		track.summarizeCoverage();
		//System.out.println("\tfitness= "+individualFitness);
		track.reset();
		elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);


	}
	 ArrayList<Chromosome> l = new ArrayList<Chromosome>(Arrays.asList(track.branchCoveredByIndividual));
	 l.removeAll(Collections.singleton(null)); 
	// if (!GAConfig.Silent) 
		 	System.out.println("\t Total Branch Covered= ( "+  l.size()  +" )");
	if (GAConfig.PrintPopulation) System.out.println(Population.toString(gaInfo));
//	if (GAConfig.SaveData)
//		Log.log(" Total Branch Covered= ( "+  l.size()  +" )");
	this.gaInfo.setPopulationFitness(l.size());
	//this.gaInfo.setLastBestFitness(this.gaInfo.getPopulationFitness());

}
private void evolve(){

	LocalSearch localSearch = GAOperationsConfig.getLocalSearch();
	localSearch.setExtraInfo(gaInfo);

	for ( int it = 1; (it<=10000 && elapsed < TestTimeout) ; it++) {
//		track.summarizeCoverage();
		Chromosome[] population = Population.generateNewPopulation(this.gaInfo,TestCluster);
		
	
		gaInfo.setPopulation(population);
		this.gaInfo.setGeneration(this.gaInfo.getGeneration() + 1);	
		this.execute();
		
		if(this.gaInfo.getLastBestFitness() >= this.gaInfo.getPopulationFitness())
			this.gaInfo.setNoImprovementCount(this.gaInfo.getNoImprovementCount() + 1);
		else
			this.gaInfo.setNoImprovementCount(0);
	

//		System.out.print(" Current Population Fitness: " +this.gaInfo.getPopulationFitness());
//		System.out.println(" Last Best Fitness: " +this.gaInfo.getLastBestFitness() );
		
	   // if (GAConfig.SaveData) Log.log("No Improvement Count:" + this.gaInfo.getNoImprovementCount()+ "\n" );
		this.gaInfo.setLastBestFitness(this.gaInfo.getPopulationFitness());
		// perform the improvement
		if(this.gaInfo.getNoImprovementCount()>=1) {
			this.gaInfo.setNoImprovementCount(0);
		//HillClimbing
		for (int i = 0; (i < GAConfig.localSearchNum) && (elapsed < TestTimeout); i++) {
			Chromosome improvedIndividual =localSearch.execute();
			if(improvedIndividual!=null)
				population[population.length-(1+i)]=improvedIndividual;
			elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);
		}
		}
		elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);
	}


}
public void start() {
	try {

		startTime = Calendar.getInstance().getTimeInMillis();
		elapsed = Calendar.getInstance().getTimeInMillis() - startTime;

		this.gaInfo.setTestTimeOut(TestTimeout);
		this.gaInfo.setStartTime(startTime);

		this.initialize();
		//Calculate fitness of initial population
		 this.execute();
		//iteratively recombined and mutated to evolve successive populations
		 this.evolve();
		 
		 System.out.println ("Done testing at generation: " + gaInfo.getGeneration());
		 System.out.println("Maximum test time exceeded at " + elapsed );			 

		 if (GAConfig.SaveData) Log.log ("Done testing at generation: " + gaInfo.getGeneration());
		 if (GAConfig.SaveData) Log.log("Maximum test time exceeded at " + elapsed );
		//track.summarizeCoverage("Memetic Algorithm class");
//		 System.out.println("\t\tMemetic Algorithm class");
//		System.out.println("\t\t----------------------");
//		System.out.println("total fitness: "+ this.gaInfo.getPopulationFitness());
//		System.out.println("----------------------");		
		 if (!GAConfig.Silent) 
			 	System.out.println(track.toString());
//		System.out.println(track.displayCondition());
		if (GAConfig.SaveData) 
			Log.log(track.toString());
		 
	} catch (Exception e) {
		logger.log(Level.SEVERE, "Something went wrong: " + e.getMessage(), e);
		}
	
		
	}
private static void experiment_configure_options(long TestTimeout,int j) {
	Log.log("Start testing #..."+j);
	Log.log ("      Seed              : "+randomseed);
}
public static void writeLogFile(int i ){
	String logFilePath = null;
	String fileName = new SimpleDateFormat("MM_dd_yyyy_hhmm'.txt'")
		.format(new Date());
	
String cName = GAConfig.classFilePath.substring(0,GAConfig.classFilePath.lastIndexOf("/") );
cName = cName.substring(cName.lastIndexOf("/") +1);
//cName=GAConfig.classFilePath;

if(GAConfig.localSearchNum!=0)
	logFilePath = "./logFiles/"+ cName+ "_MG_"+i+ "_N_" + GAConfig.N+ "_"+ fileName;
else
	logFilePath = "./logFiles/"+ cName+ "_GA_"+i+ "_N_" + GAConfig.N+ "_"+ fileName;
	
if (logFilePath != null) {
	Log.logConfig(logFilePath);
//	experiment_configure_options(TestTimeout);
	System.out.println("Log file place: "+ new File(logFilePath).getAbsolutePath());
	}
	Log.log("       SUT               : "+ GAConfig.classFilePath);
	Log.log ("      Population Size   : "+GAConfig.PopulationSize);
	Log.log ("      TestCase Size     : "+GAConfig.TestCaseSize);
	Log.log ("      localSearchNum     : "+GAConfig.localSearchNum);
	Log.log ("      N   			: "+GAConfig.N);
	Log.log ("      Timeout           : "+TestTimeout/60000 + " minutes  " + TestTimeout+" ms");
	Log.log ("");

}
//the random seed
public static long randomseed;// = (int) GAConfig.RANDOM_SEED;
private void setTestTimeout(int i){
	if(i!=0)
	 TestTimeout = 60 * 1000*i;
	else
		 TestTimeout = 60 * 500;
		
	 
}
	public static void main(String [] args) throws IOException{
	for(int i=0;i<=0;i+=4){
		MemeticAlgorithm algorithm = new MemeticAlgorithm();
		algorithm.setTestTimeout(i);
		if (GAConfig.SaveData)
			writeLogFile(i);
	     //set the random generation seeds

	      for (int j = 1; j <= 1; j += 1) {
	    	  System.out.println("Start testing #..."+j);
				//set the random generation seeds
		    	  randomseed= System.currentTimeMillis();
//	    	 randomseed=1380266417599L;
		   	  ValuesGenerator.reset(randomseed);
		//	ValuesGenerator.reset(1380058572446L);

//			if (randomseed != GAConfig.RANDOM_SEED) {
//				System.out.println("Change to a new seed: " + randomseed);
//			}

			if (GAConfig.SaveData)
				experiment_configure_options(TestTimeout,j);

			algorithm.start();

			algorithm.track.setTrackerToNull();
			algorithm = new MemeticAlgorithm();
//			if(randomseed==20)
//				algorithm.setTestTimeout();

		}
			if (GAConfig.SaveData)
				Log.log.close();
	}

	}

    
	
}
